package com.jidolstar.astro
{
	/**
	 * 3D 표현을 위한 Matrix
	 * 퍼포먼스를 향상시키기 위해 최대한 노력했음. 
	 * 
	 * @author Yongho,Ji (jidolstar@gmail.com)
	 * @since 2009.04.07
	 */ 
	public class ALMatrix3D
	{
		private var m11:Number;	//1행 1열
		private	var m12:Number;	//1행 2열
		private	var m13:Number;	//1행 3열
		private	var m14:Number;	//1행 4열
		private	var m21:Number;	//2행 1열 
		private	var m22:Number;	//2행 2열
		private	var m23:Number;	//2행 3열
		private	var m24:Number;	//2행 4열
		private	var m31:Number;	//3행 1열
		private	var m32:Number;	//3행 2열
		private	var m33:Number;	//3행 3열
		private	var m34:Number;	//3행 4열
		private	var m41:Number;	//4행 1열
		private	var m42:Number;	//4행 2열
		private	var m43:Number;	//4행 3열
		private	var m44:Number;	//4행 4열 
		
		/**
		 *  생성자 : 기본적으로 단위행렬로 만들어 준다.
		 * @param ...args
		 * <ul>
		 * 	<li> args.length = 16일때 4x4 행렬을 구성한다.
		 * 	<li> args.length = 9일때 3x3행렬을 구성한다.
		 * 	<li> 그밖에는 단위행렬을 구성한다.
		 * </ul>
		 */ 
		public function ALMatrix3D( ...args )
		{
			if( args.length == 16 )
			{
				setMatrix(  args[0], args[1], args[2], args[3],
							args[4], args[5], args[6], args[7],  
							args[8], args[9], args[10], args[11],
							args[12], args[13], args[14], args[15] );
			}
			else if( args.length == 9 )
			{
				setMatrix(  args[0], args[1], args[2], 0,
							args[3], args[4], args[5], 0,  
							args[6], args[7], args[8], 0,
							0, 0, 0, 1 );				
			}
			else
			{
				setMatrix(  1, 0, 0, 0,
							0, 1, 0, 0,
							0, 0, 1, 0,
							0, 0, 0, 1 );
			}
		}
		
		/**
		 * 각 값을 Set해준다.
		 */
		protected function setMatrix(	x11:Number, x12:Number, x13:Number, x14:Number,
										x21:Number, x22:Number, x23:Number, x24:Number,
										x31:Number, x32:Number, x33:Number, x34:Number,
										x41:Number, x42:Number, x43:Number, x44:Number) :void
		{
			this.m11 = x11;
			this.m12 = x12;
			this.m13 = x13;
			this.m14 = x14;
			this.m21 = x21;
			this.m22 = x22;
			this.m23 = x23;
			this.m24 = x24;
			this.m31 = x31;
			this.m32 = x32;
			this.m33 = x33;
			this.m34 = x34;
			this.m41 = x41;
			this.m42 = x42;
			this.m43 = x43;
			this.m44 = x44;
		}
		
		/**
		 * 행렬 요소의 값 리턴
		 */ 
		public function getElement(row:int, col:int):Number
		{
			switch( row )
			{
				case 1:
					switch( col )
					{
						case 1:return m11; 
						case 2:return m12; 
						case 3:return m13; 
						case 4:return m14;
						default:throw new ArgumentError( "row, col 인자값은 [1,4]이내 값이여아 한다." ); 
					}
					break;
				case 2:
					switch( col )
					{
						case 1:return m21; 
						case 2:return m22; 
						case 3:return m23; 
						case 4:return m24;
						default:throw new ArgumentError( "row, col 인자값은 [1,4]이내 값이여아 한다." ); 
					}
					break;
				case 3:
					switch( col )
					{
						case 1:return m31; 
						case 2:return m32; 
						case 3:return m33; 
						case 4:return m34;
						default:throw new ArgumentError( "row, col 인자값은 [1,4]이내 값이여아 한다." ); 
					}
					break;
				case 4:
					switch( col )
					{
						case 1:return m41; 
						case 2:return m42; 
						case 3:return m43; 
						case 4:return m44;
						default:throw new ArgumentError( "row, col 인자값은 [1,4]이내 값이여아 한다." ); 
					}
					break;
				default:
					throw new ArgumentError( "row, col 인자값은 [1,4]이내 값이여아 한다." ); 
					break;
			}
			return NaN;	
		}

		/**
		 * 행렬 요소의 값 설정
		 */ 
		public function setElement(row:int, col:int, value:Number):void
		{
			switch( row )
			{
				case 1:
					switch( col )
					{
						case 1:m11 = value;break;
						case 2:m12 = value;break;
						case 3:m13 = value;break;
						case 4:m14 = value;break;
						default:throw new ArgumentError( "row, col 인자값은 [1,4]이내 값이여아 한다." ); break;
					}
					break;
				case 2:
					switch( col )
					{
						case 1:m21 = value;break;
						case 2:m22 = value;break;
						case 3:m23 = value;break;
						case 4:m24 = value;break;
						default:throw new ArgumentError( "row, col 인자값은 [1,4]이내 값이여아 한다." ); break;
					}
					break;
				case 3:
					switch( col )
					{
						case 1:m21 = value;break;
						case 2:m22 = value;break;
						case 3:m23 = value;break;
						case 4:m24 = value;break;
						default:throw new ArgumentError( "row, col 인자값은 [1,4]이내 값이여아 한다." ); break;
					}
					break;
				case 4:
					switch( col )
					{
						case 1:m21 = value;break;
						case 2:m22 = value;break;
						case 3:m23 = value;break;
						case 4:m24 = value;break;
						default:throw new ArgumentError( "row, col 인자값은 [1,4]이내 값이여아 한다." ); break;
					}
					break;
				default:
					throw new ArgumentError( "row, col 인자값은 [1,4]이내 값이여아 한다." ); 
					break;					
			}
		}
		
		/**
		 * 단위 행렬로 만들어 줌
		 */ 
		public function setIdentity():void
		{
			this.setMatrix(	1,0,0,0,
							0,1,0,0,
							0,0,1,0,
							0,0,0,1);
		}

		/**
		 * Null 행렬로 만들어 줌
		 */ 		
		public function setNull():void
		{
			this.setMatrix(	0,0,0,0,
							0,0,0,0,
							0,0,0,0,
							0,0,0,0);
		}
		
		public function clone():ALMatrix3D
		{
			return new ALMatrix3D();
		}
	
		/**
		 * X축 회전행렬로 만들기
		 */ 
		public function setXAxisRotation(angleRadian:Number):void
		{
			var s:Number = Math.sin(angleRadian);
			var c:Number = Math.cos(angleRadian);
			this.setMatrix(	1, 0, 0, 0,
							0, c, s, 0,
							0, -s, c,0,
							0, 0, 0, 1);
		}	
		
		/**
		 * Y축 회전행렬로 만들기
		 */ 
		public function setYAxisRotation(angleRadian:Number):void
		{
			var s:Number = Math.sin(angleRadian);
			var c:Number = Math.cos(angleRadian);
			this.setMatrix(	c, 0, -s, 0,
							0, 1, 0, 0,
							s, 0, c,0,
							0, 0, 0, 1);
		}	
		
		/**
		 * Z축 회전행렬로 만들기
		 */ 
		public function setZAxisRotation(angleRadian:Number):void
		{
			var s:Number = Math.sin(angleRadian);
			var c:Number = Math.cos(angleRadian);
			this.setMatrix(	c, s, 0, 0,
							-s, c, 0, 0,
							0, 0, 1, 0,
							0, 0, 0, 1);
		}	
		
		/**
		 * 이동행렬로 만들기
		 */ 
		public function setTranslation(x:Number,y:Number,z:Number):void
		{
			this.setMatrix(	1, 0, 0, x,
							0, 1, 0, y,
							0, 0, 1, z,
							0, 0, 0, 1);
		}	
			
		/**
		 * 확대,축소 행렬로 만들기
		 */ 
		public function setScaling(Sx:Number, Sy:Number, Sz:Number):void
		{
			this.setMatrix(	Sx, 0, 0, 0,
							0, Sy, 0, 0,
							0, 0, Sz, 0,
							0, 0,  0, 1);
		}
			
		/**
		 * 새로운 행렬 = 행렬(인자1) * 행렬(인자2)
		 */ 
		public static function multiplyMatrix( m1:ALMatrix3D, m2:ALMatrix3D ):ALMatrix3D
		{
			var m3:ALMatrix3D = new ALMatrix3D();
			var val:Number=0;
			
			m3.m11 = m1.m11 * m2.m11 + m1.m12 * m2.m21 + m1.m13 * m2.m31 + m1.m14 * m2.m41;
			m3.m12 = m1.m11 * m2.m12 + m1.m12 * m2.m22 + m1.m13 * m2.m32 + m1.m14 * m2.m42;
			m3.m13 = m1.m11 * m2.m13 + m1.m12 * m2.m23 + m1.m13 * m2.m33 + m1.m14 * m2.m43;
			m3.m14 = m1.m11 * m2.m14 + m1.m12 * m2.m24 + m1.m13 * m2.m34 + m1.m14 * m2.m44;

			m3.m21 = m1.m21 * m2.m11 + m1.m22 * m2.m21 + m1.m23 * m2.m31 + m1.m24 * m2.m41;
			m3.m22 = m1.m21 * m2.m12 + m1.m22 * m2.m22 + m1.m23 * m2.m32 + m1.m24 * m2.m42;
			m3.m23 = m1.m21 * m2.m13 + m1.m22 * m2.m23 + m1.m23 * m2.m33 + m1.m24 * m2.m43;
			m3.m24 = m1.m21 * m2.m14 + m1.m22 * m2.m24 + m1.m23 * m2.m34 + m1.m24 * m2.m44;

			m3.m31 = m1.m31 * m2.m11 + m1.m32 * m2.m21 + m1.m33 * m2.m31 + m1.m34 * m2.m41;
			m3.m32 = m1.m31 * m2.m12 + m1.m32 * m2.m22 + m1.m33 * m2.m32 + m1.m34 * m2.m42;
			m3.m33 = m1.m31 * m2.m13 + m1.m32 * m2.m23 + m1.m33 * m2.m33 + m1.m34 * m2.m43;
			m3.m34 = m1.m31 * m2.m14 + m1.m32 * m2.m24 + m1.m33 * m2.m34 + m1.m34 * m2.m44;

			m3.m41 = m1.m41 * m2.m11 + m1.m42 * m2.m21 + m1.m43 * m2.m31 + m1.m44 * m2.m41;
			m3.m42 = m1.m41 * m2.m12 + m1.m42 * m2.m22 + m1.m43 * m2.m32 + m1.m44 * m2.m42;
			m3.m43 = m1.m41 * m2.m13 + m1.m42 * m2.m23 + m1.m43 * m2.m33 + m1.m44 * m2.m43;
			m3.m44 = m1.m41 * m2.m14 + m1.m42 * m2.m24 + m1.m43 * m2.m34 + m1.m44 * m2.m44;

			return m3;	
		}
		
		/**
		 * 새로운 백터 = 행렬 * 백터
		 * 이동,회전,확대가 모두 적용됨  
		 */ 
		public function multiply4x4ByVector3D( vectorArray:Array ):Array
		{
			var newVectorArray:Array = [];
			var v:ALVector3D;
			var newVector:ALVector3D;
			var x:Number;
			var y:Number;
			var z:Number;
			
			for each( v in vectorArray )
			{
				x = v.x;
				y = v.y;
				z = v.z;
				newVector = new ALVector3D( m11 * x + m12 * y + m13 * z + m14, 
											m21 * x + m22 * y + m23 * z + m24,
											m31 * x + m32 * y + m33 * z + m34 );
				newVectorArray.push( newVector );	
			}
			return newVectorArray;									
		}
		
		/**
		 * 3 x 3 행렬 부분만 곱셈처리한다. 
		 * 이는 scale 및 회전의 경우만 적용된다.
		 * 퍼포먼스 향상을 위해 4x4 곱보다는 3x3곱이 빠르므로 필요한 경우 사용하는 것이 좋겠다.  
		 */ 
		public function multiply3x3ByVector3D( vectorArray:Array ):Array
		{
			var newVectorArray:Array = [];
			var v:ALVector3D;
			var newVector:ALVector3D;
			var x:Number;
			var y:Number;
			var z:Number;
			
			for each( v in vectorArray )
			{
				x = v.x;
				y = v.y;
				z = v.z;
				newVector = new ALVector3D( m11 * x + m12 * y + m13 * z, 
											m21 * x + m22 * y + m23 * z,
											m31 * x + m32 * y + m33 * z );
				newVectorArray.push( newVector );	
			}
			return newVectorArray;	
		}
		
		
		/**
		 * 행렬의 값을 확인하기 위한 함수
		 */ 
		public function toString():String
		{
			var strTemp:String;
			strTemp = m11 + "	" + m12 + "	" + m13 + "	" + m14 +"\n";
			strTemp += m21 + "	" + m22 + "	" + m23 + "	" + m24 +"\n";
			strTemp += m31 + "	" + m32 + "	" + m33 + "	" + m34 +"\n";
			strTemp += m41 + "	" + m42 + "	" + m43 + "	" + m44;
			return strTemp;		
		}
	}
}